package ru.lukyanets.torrent.bencode;

import java.io.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;

/**
 * @author slukyanets
 */
public final class BEncoder {
    private final OutputStream os;

    private BEncoder(OutputStream os) {
        this.os = os;
    }

    public static void encode(Object object, OutputStream os) throws IOException {
        new BEncoder(os).encodeObject(object);
    }

    private void encodeObject(Object object) throws IOException {
        if (object instanceof Number) {
            encodeNumber((Number) object);
        } else if (object instanceof List) {
            encodeList((List<?>) object);
        } else if (object instanceof Map) {
            encodeMap((Map<?, ?>) object);
        } else if (object instanceof String) {
            encodeBytes(((String) object).getBytes(BencodeConstants.DEFAULT_CHARSET));
        } else if (object instanceof byte[]) {
            encodeBytes((byte[]) object);
        } else {
            throw new IOException("Unsupported object class " + object.getClass());
        }
    }

    private void encodeMap(Map<?, ?> map) throws IOException{
        os.write(BencodeConstants.PREFIX_MAP);
        for(Map.Entry<?, ?> entry : map.entrySet()) {
            if (entry.getKey() instanceof String key) {
                encodeBytes(key.getBytes(BencodeConstants.DEFAULT_CHARSET));
                encodeObject(entry.getValue());
            } else {
                throw new IOException("Unsupported type of Map key class " + entry.getKey().getClass());
            }
        }
        os.write(BencodeConstants.SUFFIX_END);
    }

    private void encodeList(List<?> list) throws IOException {
        os.write(BencodeConstants.PREFIX_LIST);
        for (Object object : list) {
            encodeObject(object);
        }
        os.write(BencodeConstants.SUFFIX_END);
    }

    private void encodeNumber(Number number) throws IOException {
        os.write(BencodeConstants.PREFIX_NUMBER);
        os.write(number.toString().getBytes(BencodeConstants.DEFAULT_CHARSET));
        os.write(BencodeConstants.SUFFIX_END);
    }

    private void encodeBytes(byte[] bytes) throws IOException {
        os.write(Integer.valueOf(bytes.length).toString().getBytes(BencodeConstants.DEFAULT_CHARSET));
        os.write(BencodeConstants.SPLITTER);
        os.write(bytes);
    }
}
